package com.cn.wanxi.message.socketserver;

import java.io.IOException;
import java.util.*;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;

import com.cn.wanxi.message.domain.MessageModel;
import com.cn.wanxi.message.utils.SpringUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.logging.log4j.util.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.RestController;

@RestController
@ServerEndpoint("/websocket/{username}")
public class WebSocketServer {


    //region 静态属性

    //日志输出对象
    private static Logger log = LoggerFactory.getLogger(WebSocketServer.class);

    //静态变量，用来记录当前在线连接数。应该把它设计成线程安全的。
    private static int onlineCount = 0;


    //用于存放登录用户与其对应客户端的封装对象
    private static Map<String, WebSocketServer> usernameWebSocketServerMap = new Hashtable<>();


    //endregion


    //region 私有属性


    //用于将java对象与JSON字符串的互相转换
    private ObjectMapper objectMapper;

    //用于操作redis的模板对象
    private RedisTemplate redisTemplate;


    //与某个客户端的连接会话，需要通过它来给客户端发送数据
    private Session session;

    //登录的用户名
    private String username;

    /**
     * @MethodName removeOnlineUser
     * @Description 根据用于名将在线用户移出
     * @Param [username, msg]
     * @Return void
     **/
    public static void removeOnlineUser(String username, String msg) {

        if (usernameWebSocketServerMap.containsKey(username)) {
            //移除对应的websocket
            usernameWebSocketServerMap.remove(username);
            //在线人数减一
            subOnlineCount();

            if (!Strings.isEmpty(msg)) {
                sendMsgToAllOnlineUser(msg);
            }

        }

    }


    public static void removeOnlineUser(String username) {

        removeOnlineUser(username, null);

    }

    /**
     * @MethodName getSocketByUserName
     * @Description 根据用户名获取对应的WebSocketServer对象
     * @Param [publisher]
     * @Return com.cn.wanxi.message.socketserver.WebSocketServer
     **/
    public static WebSocketServer getSocketByUserName(String publisher) {

        if (publisher == null || "".equals(publisher)) {
            return null;
        }

        return usernameWebSocketServerMap.getOrDefault(publisher, null);
    }


    //endregion


    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("username") String username) throws IOException, EncodeException {

        //region 初始化私有属性
        objectMapper = SpringUtil.getBean(ObjectMapper.class);

        redisTemplate = (RedisTemplate) SpringUtil.getBean("redisTemplate");

        this.session = session;

        this.username = username;
        //endregion

        //region 更改静态属性

        //添加用户名与客户服务端的映射
        usernameWebSocketServerMap.put(username, this);


        //在线人数加一
        addOnlineCount();

        log.info("用户名为\t{}\t所登录的客户端连接上了服务器,当前上线人数为\t{}\t", username, getOnlineCount());

        //endregion


        log.info("当前登录的用户名如下：");
        Set<String> loginUserNames = usernameWebSocketServerMap.keySet();

        loginUserNames.forEach((item) -> {
            log.info(item);
        });

        //region 发送消息给客户端
        //1.创建返回的消息对象模型
        MessageModel messageModel = new MessageModel();

        messageModel.setCode(0);

        //2.设置需要发送的消息数据，这里是将在线的用户名发送了出去
        messageModel.setMessage(loginUserNames);

        //3.设置消息的发送者
        messageModel.setPublisher("Server");

        //设置消息的接收者
        messageModel.setReceiver("all-online-user");

        sendMsgToAllOnlineUser(objectMapper.writeValueAsString(messageModel));

        //endregion

    }


    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {

        removeOnlineUser(this.username);
        log.info("用户名为\"{}\"的客户端连接关闭！当前在线人数为\"{}\"", username, getOnlineCount());
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, Session session) {

        log.info("名为{}的用户所对应的客户端发来消息", message);

    }

    /**
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        //如果连接对象在映射关系中
        removeOnlineUser(this.username);
        log.error("用户名为\"{}\"的客户端建立连接时出现错误！具体原因如下\r\n{}", this.username, error.getMessage());
        error.printStackTrace();

    }

    /**
     * 实现服务器主动推送
     */
    public void sendMessage(Object message) throws IOException, EncodeException {
        synchronized (this.session) {
            this.session.getBasicRemote().sendObject(message);
        }


    }

    public void sendMessage(String message) throws IOException {
        synchronized (this.session) {
            this.session.getBasicRemote().sendText(message);
        }
    }


    /**
     * 群发自定义消息
     */
    public static void sendMsgToAllOnlineUser(String message) {

        //获取在线的用户集合
        Collection<WebSocketServer> onlineUsers = usernameWebSocketServerMap.values();

        //遍历给用户发送信息
        onlineUsers.forEach((usr) -> {
            try {
                usr.sendMessage(message);
            } catch (IOException e) {
                log.info("给用户{}发送消息时出现错误！！错误原因如下:\r\n{}", usr.username, e.getMessage());
            }
        });


    }

    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

    public static synchronized void addOnlineCount() {
        WebSocketServer.onlineCount++;
    }

    public static synchronized void subOnlineCount() {
        WebSocketServer.onlineCount--;
    }

    /**
     * @MethodName sendMessageToUser
     * @Description 给指定用户发消息
     * @Param [msgModel]
     * @Return boolean
     **/
    public boolean sendMessageToUser(MessageModel msgModel) {

        String receiver = msgModel.getReceiver();
        WebSocketServer receiverSocket = getSocketByUserName(receiver);
        if (Objects.isNull(receiverSocket)) {
            return false;
        }
        try {


            //给接收者发送消息
            receiverSocket.sendMessage(objectMapper.writeValueAsString(msgModel));
            return true;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;

    }
}